////////////////////////////////////////////////////////////////////////////////
// Copyright 2010 Michael Schmalle - Teoti Graphix, LLC
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0 
// 
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, 
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and 
// limitations under the License
// 
// Author: Michael Schmalle, Principal Architect
// mschmalle at teotigraphix dot com
////////////////////////////////////////////////////////////////////////////////

package fxplugins.core
{

import flash.events.Event;
import flash.events.EventDispatcher;

import fxplugins.core.restricted.InjectorProvider;
import fxplugins.plugin.IPluginMap;
import fxplugins.plugin.Plugin;
import fxplugins.plugin.PluginMap;
import fxplugins.registry.ExtensionRegistryProvider;
import fxplugins.registry.IExtensionRegistry;
import fxplugins.utility.IUtilityMap;
import fxplugins.utility.IUtilityProvider;
import fxplugins.utility.UtilityMap;

import mx.core.IMXMLObject;
import mx.events.FlexEvent;

/**
 * The default implementation of the <code>IComponentMap</code> interface.
 * 
 * <p>The componentMap is the central repository for utilities and plugins.</p>
 * 
 * <p>A <strong>utility</strong> is generally a singleton manager that acts 
 * on specific types of extension points declared within plugins.</p>
 * 
 * <p>A <strong>plugin</strong> is a repository of extension points and 
 * extensions defined within a specific unique string identifier.
 * namespace. This allows many 3rd party plugins to be defined in one or more
 * applications.</p>
 * 
 * @author Michael Schmalle
 * @copyright Teoti Graphix, LLC
 * @productversion 1.0
 */
public class ComponentMap extends EventDispatcher 
	implements IComponentMap, IMXMLObject, IDisposable
{
	//--------------------------------------------------------------------------
	//
	//  IComponentMap API :: Properties
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  application
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _application:Object;
	
	/**
	 * @copy fxplugins.core.IComponentMap#application
	 */
	public function get application():Object
	{
		return _application;
	}
	
	//----------------------------------
	//  manifest
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _manifest:IComponentManifest;
	
	/**
	 * @copy fxplugins.core.IComponentMap#manifest
	 */
	public function get manifest():IComponentManifest
	{
		return _manifest;
	}
	
	/**
	 * @private
	 */
	public function set manifest(value:IComponentManifest):void
	{
		_manifest = value;
	}
	
	//----------------------------------
	//  utilityMap
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _utilityMap:IUtilityMap;
	
	/**
	 * @copy fxplugins.core.IComponentMap#utilityMap
	 */
	public function get utilityMap():IUtilityMap
	{
		return _utilityMap;
	}
	
	/**
	 * @private
	 */
	public function set utilityMap(value:IUtilityMap):void
	{
		if (_utilityMap)
			_utilityMap.componentMap = null;
		
		_utilityMap = value;
		_utilityMap.componentMap = this;
	}
	
	//----------------------------------
	//  pluginMap
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _pluginMap:IPluginMap;
	
	/**
	 * @copy fxplugins.core.IComponentMap#pluginMap
	 */
	public function get pluginMap():IPluginMap
	{
		return _pluginMap;
	}
	
	public function set pluginMap(value:IPluginMap):void
	{
		if (_pluginMap)
			_pluginMap.componentMap = null;
		
		_pluginMap = value;
		_pluginMap.componentMap = this;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Public API :: Properties
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  extensionRegistryProvider
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _extensionRegistryProvider:IUtilityProvider;
	
	/**
	 * Used to create the runtime extension registry.
	 * 
	 * <p>This property would typically to be set in the MXML declaration.</p>
	 */
	public function get extensionRegistryProvider():IUtilityProvider
	{
		return _extensionRegistryProvider;
	}
	
	/**
	 * @private
	 */	
	public function set extensionRegistryProvider(value:IUtilityProvider):void
	{
		if (_extensionRegistryProvider == value)
			return;
		
		_extensionRegistryProvider = value;
	}
	
	//----------------------------------
	//  injectorProvider
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _injectorProvider:IUtilityProvider;
	
	/**
	 * Used to create the runtime injector.
	 * 
	 * <p>This property would typically to be set in the MXML declaration.</p>
	 */
	public function get injectorProvider():IUtilityProvider
	{
		return _injectorProvider;
	}
	
	/**
	 * @private
	 */	
	public function set injectorProvider(value:IUtilityProvider):void
	{
		if (_injectorProvider == value)
			return;
		
		_injectorProvider = value;
	}
	
	//----------------------------------
	//  extensionRegistry
	//----------------------------------
	
	/**
	 * @private
	 */	
	private var _extensionRegistry:IExtensionRegistry;
	
	/**
	 * Returns the component map's <code>IExtensionRegistry</code> utility.
	 */
	public function get extensionRegistry():IExtensionRegistry
	{
		return _extensionRegistry;
	}
	
	//----------------------------------
	//  injector
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _injector:IComponentInjector;
	
	/**
	 * Returns the component map's <code>injector</code> utility.
	 */
	public function get injector():IComponentInjector
	{
		return _injector;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 */
	public function ComponentMap()
	{
		super();
	}
	
	//--------------------------------------------------------------------------
	//
	//  IMXMLObject API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	public function initialized(document:Object, id:String):void
	{
		// save a reference to the main application
		_application = document;
		
		// all initialization happens on application complete
		_application.addEventListener(
			FlexEvent.INITIALIZE, initializeHandler);
	}
	
	//--------------------------------------------------------------------------
	//
	//  IDisposable API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @copy fxplugin.core.IDisposable#dispose()
	 */
	public function dispose():void
	{
		// dispose of the pluginMap
		if (_pluginMap is IDisposable)
			IDisposable(_pluginMap).dispose();
		
		// dispose of the utilityMap
		if (_utilityMap is IDisposable)
			IDisposable(_utilityMap).dispose();
		
		// remove references
		_application = null;
		_manifest = null;
		_pluginMap = null;
		_utilityMap = null;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Public :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Initializes the component map.
	 * 
	 * <p>This method is called by default in the applicationComplete handler
	 * for the main application. If the instance is constructed with actionscript,
	 * call this method passing the application instance.</p>
	 * 
	 * @param application The Application instance hosting this component map.
	 */
	public function initialize(application:Object):void
	{
		// save a reference to the Application that loaded the map
		_application = application;
		
		// if the registry provider has not been specified, use the 
		// default ExtensionRegistry
		if (!_extensionRegistryProvider)
			_extensionRegistryProvider = new ExtensionRegistryProvider();
		
		// if the injector provider has not been specified, use the 
		// default Injector
		if (!_injectorProvider)
			_injectorProvider = new InjectorProvider();
		
		// component map startup
		// register all utilities with the global utility map
		initializeUtilities();
		
		// parse all plugins
		//  - parse extension points, extensions and any utilities 
		//    the plugin declares
		initializePlugins();
		// parse the application manifest
		initializeManifest();
		// dispatch initializeComplete event
		initializeComplete();
	}
	
	//--------------------------------------------------------------------------
	//
	//  Protected :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Initialize the application map's utility map.
	 * 
	 * <p>If no <code>utilityMap</code> is declared, a new one is created.
	 * This is becasue if <code>IPlugin</code> declares it's own utilities
	 * in it's MXML decalration, they will be added here at startup.
	 */
	protected function initializeUtilities():void
	{
		// if not utility map has been assigned create one
		if (_utilityMap == null)
			utilityMap = new UtilityMap();
		
		// registers any component map utilities
		initializeStartupUtilities();
		
		if (_pluginMap != null && _pluginMap.plugins != null)
		{
			// check to see if any plugins in the map contain utilities
			var plugins:Array = _pluginMap.plugins;
			var leni:int = plugins.length;
			for (var i:int = 0; i < leni; i++)
			{
				// if a plugin declared utilities add them to the map
				var plugin:Plugin = plugins[i] as Plugin;
				if (plugin.utilities != null)
				{
					var lenj:int = plugin.utilities.length;
					for (var j:int = 0; j < lenj; j++)
					{
						UtilityMap(_utilityMap).utilities.push(plugin.utilities[j]);
					}
				}
				// empty the references, since we have traded them 
				// to the component map
				plugin.utilities = [];
			}
		}
		
		// creates -> puts all -> dispatches(UTILITY_ADD)
		_utilityMap.initialize();
	}
	
	/**
	 * Initializes the IExtensionRegistry used to manage all plugins.
	 */
	protected function initializeStartupUtilities():void
	{
		// create the core extension registry
		_extensionRegistry = 
			_extensionRegistryProvider.getInstance() as IExtensionRegistry;
		
		// create the core injector
		_injector = _injectorProvider.getInstance() as IComponentInjector;
		
		// add the utilities
		_utilityMap.putUtility(IExtensionRegistry, _extensionRegistry);
		_utilityMap.putUtility(IComponentInjector, _injector);
	}
	
	/**
	 * Second the plugin map is initialized.
	 */
	protected function initializePlugins():void
	{
		// all plugin utilities are registered
		if (_pluginMap == null)
			_pluginMap = new PluginMap();
		
		var registry:IExtensionRegistry = _utilityMap.
			getUtility(IExtensionRegistry) as IExtensionRegistry;
		
		// all startup plugins, extension points and extensions are added
		// use the extension registry to parse all extension points and extensions
		// this adds them to the IExtensionRegistry
		registry.initialize(_pluginMap);
	}
	
	/**
	 * Third the manifest is initialized.
	 */
	protected function initializeManifest():void
	{
	}
	
	/**
	 * Fourth the map dispatches initializeComplete.
	 */
	protected function initializeComplete():void
	{
		dispatchEvent(new Event("initializeComplete"));
	}
	
	//--------------------------------------------------------------------------
	//
	//  Private :: Handlers
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private function initializeHandler(event:Event):void
	{
		_application.removeEventListener(
			FlexEvent.INITIALIZE, initializeHandler);
		
		initialize(_application);
	}
}
}